package me.jtx.poplar.user.filter;

import java.io.IOException;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.filter.OncePerRequestFilter;

import me.jtx.poplar.core.exception.auth.AuthCredentialsMissingException;
import me.jtx.poplar.core.exception.auth.AuthenticationFailedException;
import me.jtx.poplar.core.exception.database.NotFoundException;
import me.jtx.poplar.user.Key;
import me.jtx.poplar.user.Props;
import me.jtx.poplar.user.Route;
import me.jtx.poplar.user.model.entity.User;
import me.jtx.poplar.user.service.UserService;

/**
 * Filter to process user authentication for the incoming service requests
 *
 * @author: Tong Huazhong(jsyzthz@gmail.com)
 * @copyright: &copy; 2016, Tong Huazhong
 */
public class UserAuthFilter extends OncePerRequestFilter {
	private Logger log = LoggerFactory.getLogger(UserAuthFilter.class);
	private @Autowired UserService userService;
	private @Autowired Props props;

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		String authorizationHeader = request.getHeader("authorization");
		String authenticationHeader = request.getHeader("authentication");
		String route;
		String suffix;

		if (request.getRequestURI().endsWith(".xml")) {
			route = request.getRequestURI().substring(0, request.getRequestURI().lastIndexOf(".xml"));
			suffix = ".xml";
		} else if (request.getRequestURI().endsWith(".json")) {
			route = request.getRequestURI().substring(0, request.getRequestURI().lastIndexOf(".json"));
			suffix = ".json";
		} else {
			route = request.getRequestURI();
			suffix = ".xml";
		}

		// Checking whether the current route needs to be authenticated
		log.info("************************" + route);
		if (/*props.apiAuthRoutes.contains(route) || props.webAuthRoutes.contains(route)*/false) {
			try {
				// Check for authorization header presence
				String authHeader = null;
				if (authorizationHeader == null || authorizationHeader.equalsIgnoreCase("")) {
					if (authenticationHeader == null || authenticationHeader.equalsIgnoreCase("")) {
						authHeader = null;
					} else {
						authHeader = authenticationHeader;
						log.info("authentication: " + authenticationHeader);
					}
				} else {
					authHeader = authorizationHeader;
					log.info("authorization: " + authorizationHeader);
				}

				if (authHeader != null && !authHeader.equalsIgnoreCase("")) {
					String[] creds = authHeader.split(" ");

					// Check whether Basic Auth
					if (creds[0].equalsIgnoreCase("basic")) {
						String credString = new String(Base64.decodeBase64(creds[1].getBytes()));
						String[] userPass = credString.split(":");

						// Authenticate the credentials
						if (userPass != null && userPass.length > 0) {
							User user;
							if (request.getSession(false) != null
									&& request.getSession(false).getAttribute(Key.userInSession) != null) {
								user = (User) request.getSession(false).getAttribute(Key.userInSession);
								authenticateUserFromSession(user, userPass, request);
							} else
								authenticateUser(userPass, request);

							// Now since the authentication process if finished
							// move the request forward
							filterChain.doFilter(request, response);
						} else {
							throw new AuthCredentialsMissingException("");
						}
					} else {
						throw new AuthCredentialsMissingException("");
					}
				} else {
					throw new AuthCredentialsMissingException("");
				}

			} catch (AuthenticationFailedException ae) {
				response.setHeader("content-type", request.getHeader("content-type"));
				response.sendRedirect(props.fApiPath + Route.Api.errorController + Route.Api.authFailedUrl + suffix);
				return;
			} catch (AuthCredentialsMissingException ae) {
				response.setHeader("content-type", request.getHeader("content-type"));
				response.sendRedirect(props.fApiPath + Route.Api.errorController + Route.Api.credsMissingUrl + suffix);
				return;
			} catch (NotFoundException nfe) {
				response.setHeader("content-type", request.getHeader("content-type"));
				response.sendRedirect(props.fApiPath + Route.Api.errorController + Route.Api.authFailedUrl + suffix);
				return;
			} catch (Exception e) {
				log.error(e.getMessage());
				response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
				return;
			}
		} else {
			filterChain.doFilter(request, response);
		}
	}

	private void authenticateUser(String[] userPass, HttpServletRequest request)
			throws NotFoundException, AuthenticationFailedException {
		final User user = userService.findByUsername(userPass[0]);
		log.info("User Found: " + user.getUserName());

		if (user.getPassWord().equals(User.hashPassword(userPass[1]))) {
			log.info("Authenticated: " + user.getUserName());

			// Store the attribute to be referenced later in the API code
			request.getSession(true).setAttribute(Key.userInSession, user);
		} else {
			log.info("User Authentication Failed: " + user.getUserName());
			throw new AuthenticationFailedException("");
		}
	}

	private void authenticateUserFromSession(User user, String[] userPass, HttpServletRequest request)
			throws NotFoundException, AuthenticationFailedException {
		log.info("User Found: " + user.getUserName());

		if (user.getPassWord().equals(User.hashPassword(userPass[1]))) {
			log.info("Authenticated: " + user.getUserName());
		} else {
			log.info("User Authentication Failed: " + user.getUserName());
			throw new AuthenticationFailedException("");
		}
	}
}
